#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
命令行界面
提供用户友好的命令行工具来管理定时任务
"""

import os
import sys
import json
import click
from datetime import datetime
from typing import List, Dict, Any, Optional
from tabulate import tabulate
import logging

# 添加项目根目录到Python路径，以便正确导入模块
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
if parent_dir not in sys.path:
    sys.path.insert(0, parent_dir)

# 导入模块，支持相对导入和绝对导入
try:
    from scheduler.task_scheduler import TaskScheduler, ScheduledTask
    from scheduler.config_manager import ConfigManager
    from scheduler.cron_parser import CronParser, get_cron_examples
except ImportError:
    # 如果作为包运行，尝试相对导入
    try:
        from task_scheduler import TaskScheduler, ScheduledTask
        from config_manager import ConfigManager
        from cron_parser import CronParser, get_cron_examples
    except ImportError as e:
        print(f"导入错误: {e}", file=sys.stderr)
        print("请确保项目结构正确，或使用主入口点: python cronpy.py", file=sys.stderr)
        sys.exit(1)


class CLIManager:
    """命令行管理器"""

    def __init__(self, config_dir: str = None):
        """初始化CLI管理器"""
        self.config_manager = ConfigManager(config_dir=config_dir)
        self.scheduler: Optional[TaskScheduler] = None

    def init_scheduler(self):
        """初始化调度器"""
        if self.scheduler is None:
            scheduler_config = self.config_manager.get_scheduler_config()
            self.scheduler = TaskScheduler(
                max_workers=scheduler_config.get("max_workers", 5),
                log_file=scheduler_config.get("log_file", "scheduler.log")
            )

            # 加载任务
            tasks = self.config_manager.load_all_tasks()
            for task in tasks:
                try:
                    self.scheduler.add_task(task)
                except Exception as e:
                    click.echo(f"警告: 加载任务 {task.task_id} 失败: {e}", err=True)

    def start_scheduler(self):
        """启动调度器"""
        self.init_scheduler()
        if self.scheduler:
            self.scheduler.start()
            click.echo("调度器已启动")

    def stop_scheduler(self):
        """停止调度器"""
        if self.scheduler:
            self.scheduler.stop()
            click.echo("调度器已停止")

    def save_state(self):
        """保存状态"""
        if self.scheduler:
            state_file = os.path.join(self.config_manager.config_dir, "scheduler_state.json")
            self.scheduler.save_state(state_file)
            click.echo("状态已保存")

    def load_state(self):
        """加载状态"""
        if self.scheduler:
            state_file = os.path.join(self.config_manager.config_dir, "scheduler_state.json")
            self.scheduler.load_state(state_file)
            click.echo("状态已加载")


# 全局CLI管理器实例
# 计算cronpy根目录的config路径
# 从scheduler目录向上找到cronpy根目录
cronpy_root = parent_dir  # parent_dir已经是cronpy根目录
config_dir_path = os.path.join(cronpy_root, 'config')


cli_manager = CLIManager(config_dir=config_dir_path)


# 主命令组
@click.group()
@click.option('--config-dir', default='config', help='配置文件目录')
@click.option('--verbose', '-v', is_flag=True, help='详细输出')
@click.pass_context
def cli(ctx, config_dir, verbose):
    """CronPy - Windows定时任务调度器"""
    ctx.ensure_object(dict)
    ctx.obj['config_dir'] = config_dir
    ctx.obj['verbose'] = verbose

    # 设置日志级别
    if verbose:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.WARNING)


# 任务管理子命令组
@cli.group()
def task():
    """任务管理"""
    pass


@task.command('list')
@click.option('--status', help='按状态筛选任务')
@click.option('--format', type=click.Choice(['table', 'json']), default='table', help='输出格式')
def list_tasks(status, format):
    """列出所有任务"""
    cli_manager.init_scheduler()
    tasks = cli_manager.scheduler.list_tasks() if cli_manager.scheduler else []

    if status:
        status_map = {
            'pending': 'pending',
            'running': 'running',
            'completed': 'completed',
            'failed': 'failed',
            'disabled': 'disabled'
        }
        if status.lower() in status_map:
            tasks = [t for t in tasks if t.status.value == status_map[status.lower()]]

    if format == 'json':
        task_list = []
        for t in tasks:
            task_dict = {
                'task_id': t.task_id,
                'name': t.name,
                'cron_expression': t.cron_expression,
                'command': t.command,
                'enabled': t.enabled,
                'status': t.status.value,
                'description': t.description,
                'last_run_time': t.last_run_time.isoformat() if t.last_run_time else None,
                'next_run_time': t.next_run_time.isoformat() if t.next_run_time else None
            }
            task_list.append(task_dict)
        click.echo(json.dumps(task_list, indent=2, ensure_ascii=False))
    else:
        if not tasks:
            click.echo("暂无任务")
            return

        table_data = []
        for t in tasks:
            table_data.append([
                t.task_id,
                t.name,
                t.cron_expression,
                t.status.value,
                '是' if t.enabled else '否',
                t.last_run_time.strftime('%Y-%m-%d %H:%M:%S') if t.last_run_time else '从未',
                t.next_run_time.strftime('%Y-%m-%d %H:%M:%S') if t.next_run_time else '未启用'
            ])

        headers = ['任务ID', '名称', 'Cron表达式', '状态', '启用', '最后运行', '下次运行']
        click.echo(tabulate(table_data, headers=headers, tablefmt='grid'))


@task.command('add')
@click.option('--task-id', required=True, help='任务ID')
@click.option('--name', required=True, help='任务名称')
@click.option('--cron', required=True, help='Cron表达式')
@click.option('--command', required=True, help='执行命令')
@click.option('--description', default='', help='任务描述')
@click.option('--working-dir', default='', help='工作目录')
@click.option('--timeout', type=int, help='超时时间(秒)')
@click.option('--max-retries', type=int, default=0, help='最大重试次数')
@click.option('--env', multiple=True, help='环境变量 (格式: KEY=VALUE)')
@click.option('--disabled', is_flag=True, help='创建后禁用任务')
def add_task(task_id, name, cron, command, description, working_dir, timeout,
             max_retries, env, disabled):
    """添加新任务"""
    # 验证cron表达式
    try:
        parser = CronParser(cron)
        click.echo(f"✓ Cron表达式有效: {parser.get_schedule_description()}")
    except Exception as e:
        click.echo(f"✗ Cron表达式无效: {e}", err=True)
        return

    # 解析环境变量
    environment = {}
    for env_var in env:
        if '=' in env_var:
            key, value = env_var.split('=', 1)
            environment[key] = value
        else:
            click.echo(f"警告: 无效的环境变量格式: {env_var}", err=True)

    # 创建任务配置
    task_config = {
        'task_id': task_id,
        'name': name,
        'cron_expression': cron,
        'command': command,
        'description': description,
        'working_dir': working_dir,
        'environment': environment,
        'timeout': timeout,
        'max_retries': max_retries,
        'enabled': not disabled
    }

    try:
        # 验证配置
        errors = cli_manager.config_manager.validate_task_config(task_config)
        if errors:
            click.echo("配置验证失败:", err=True)
            for error in errors:
                click.echo(f"  - {error}", err=True)
            return

        # 添加到配置
        cli_manager.config_manager.add_task_config(task_config)

        # 如果调度器正在运行，添加任务
        if cli_manager.scheduler and cli_manager.scheduler.running:
            task_obj = cli_manager.config_manager.create_task_from_config(task_config)
            cli_manager.scheduler.add_task(task_obj)

        click.echo(f"✓ 任务 '{name}' 添加成功 (ID: {task_id})")

    except Exception as e:
        click.echo(f"✗ 添加任务失败: {e}", err=True)


@task.command('update')
@click.argument('task_id')
@click.option('--name', help='任务名称')
@click.option('--cron', help='Cron表达式')
@click.option('--command', help='执行命令')
@click.option('--description', help='任务描述')
@click.option('--working-dir', help='工作目录')
@click.option('--timeout', type=int, help='超时时间(秒)')
@click.option('--max-retries', type=int, default=0, help='最大重试次数')
@click.option('--env', multiple=True, help='环境变量 (格式: KEY=VALUE)')
@click.option('--enable', is_flag=True, help='启用任务')
@click.option('--disable', is_flag=True, help='禁用任务')
def update_task(task_id, name, cron, command, description, working_dir, timeout,
                max_retries, env, enable, disable):
    """更新任务"""
    # 获取现有配置
    existing_config = cli_manager.config_manager.get_task_config(task_id)
    if not existing_config:
        click.echo(f"✗ 任务 '{task_id}' 不存在", err=True)
        return

    updates = {}

    if name is not None:
        updates['name'] = name
    if cron is not None:
        # 验证cron表达式
        try:
            parser = CronParser(cron)
            click.echo(f"✓ Cron表达式有效: {parser.get_schedule_description()}")
            updates['cron_expression'] = cron
        except Exception as e:
            click.echo(f"✗ Cron表达式无效: {e}", err=True)
            return
    if command is not None:
        updates['command'] = command
    if description is not None:
        updates['description'] = description
    if working_dir is not None:
        updates['working_dir'] = working_dir
    if timeout is not None:
        updates['timeout'] = timeout
    if max_retries is not None:
        updates['max_retries'] = max_retries

    # 处理环境变量
    if env:
        environment = existing_config.get('environment', {}).copy()
        for env_var in env:
            if '=' in env_var:
                key, value = env_var.split('=', 1)
                environment[key] = value
            else:
                click.echo(f"警告: 无效的环境变量格式: {env_var}", err=True)
        updates['environment'] = environment

    # 处理启用/禁用
    if enable and disable:
        click.echo("✗ 不能同时启用和禁用任务", err=True)
        return
    elif enable:
        updates['enabled'] = True
    elif disable:
        updates['enabled'] = False

    try:
        # 更新配置
        cli_manager.config_manager.update_task_config(task_id, updates)

        # 如果调度器正在运行，更新任务
        if cli_manager.scheduler and cli_manager.scheduler.running:
            # 获取更新后的配置
            updated_config = cli_manager.config_manager.get_task_config(task_id)
            if updated_config:
                task_obj = cli_manager.config_manager.create_task_from_config(updated_config)
                cli_manager.scheduler.update_task(task_obj)

        click.echo(f"✓ 任务 '{task_id}' 更新成功")

    except Exception as e:
        click.echo(f"✗ 更新任务失败: {e}", err=True)


@task.command('remove')
@click.argument('task_id')
@click.option('--force', '-f', is_flag=True, help='强制删除，不询问确认')
def remove_task(task_id, force):
    """删除任务"""
    # 检查任务是否存在
    existing_config = cli_manager.config_manager.get_task_config(task_id)
    if not existing_config:
        click.echo(f"✗ 任务 '{task_id}' 不存在", err=True)
        return

    if not force:
        if not click.confirm(f"确定要删除任务 '{existing_config['name']}' 吗？"):
            return

    try:
        # 从配置中移除
        cli_manager.config_manager.remove_task_config(task_id)

        # 如果调度器正在运行，从调度器中移除
        if cli_manager.scheduler and cli_manager.scheduler.running:
            cli_manager.scheduler.remove_task(task_id)

        click.echo(f"✓ 任务 '{task_id}' 删除成功")

    except Exception as e:
        click.echo(f"✗ 删除任务失败: {e}", err=True)


@task.command('enable')
@click.argument('task_id')
def enable_task(task_id):
    """启用任务"""
    try:
        cli_manager.config_manager.update_task_config(task_id, {'enabled': True})

        if cli_manager.scheduler and cli_manager.scheduler.running:
            cli_manager.scheduler.enable_task(task_id)

        click.echo(f"✓ 任务 '{task_id}' 已启用")

    except Exception as e:
        click.echo(f"✗ 启用任务失败: {e}", err=True)


@task.command('disable')
@click.argument('task_id')
def disable_task(task_id):
    """禁用任务"""
    try:
        cli_manager.config_manager.update_task_config(task_id, {'enabled': False})

        if cli_manager.scheduler and cli_manager.scheduler.running:
            cli_manager.scheduler.disable_task(task_id)

        click.echo(f"✓ 任务 '{task_id}' 已禁用")

    except Exception as e:
        click.echo(f"✗ 禁用任务失败: {e}", err=True)


@task.command('status')
@click.argument('task_id')
def task_status(task_id):
    """查看任务状态"""
    cli_manager.init_scheduler()
    if not cli_manager.scheduler:
        click.echo("调度器未初始化", err=True)
        return

    status = cli_manager.scheduler.get_task_status(task_id)
    if not status:
        click.echo(f"✗ 任务 '{task_id}' 不存在", err=True)
        return

    click.echo(f"任务ID: {status['task_id']}")
    click.echo(f"名称: {status['name']}")
    click.echo(f"状态: {status['status']}")
    click.echo(f"启用: {'是' if status['enabled'] else '否'}")
    click.echo(f"最后运行: {status['last_run_time'] or '从未'}")
    click.echo(f"下次运行: {status['next_run_time'] or '未启用'}")
    click.echo(f"重试次数: {status['retry_count']}")

    if status['results']:
        click.echo("\n最近执行结果:")
        for result in status['results'][-3:]:  # 显示最近3个结果
            click.echo(f"  {result['start_time']}: {result['status']} ({result['exit_code']})")


@task.command('run')
@click.argument('task_id')
def run_task(task_id):
    """立即执行任务"""
    cli_manager.init_scheduler()
    if not cli_manager.scheduler:
        click.echo("调度器未初始化", err=True)
        return

    task = cli_manager.scheduler.get_task(task_id)
    if not task:
        click.echo(f"✗ 任务 '{task_id}' 不存在", err=True)
        return

    click.echo(f"正在执行任务: {task.name}")
    # 立即执行任务并等待完成
    future = cli_manager.scheduler.executor.submit(cli_manager.scheduler._run_task_async, task)
    # 等待任务完成
    future.result()
    click.echo("任务执行完成")


@task.command('templates')
def task_templates():
    """显示任务模板"""
    templates = cli_manager.config_manager.get_task_templates()

    click.echo("可用的任务模板:")
    click.echo()

    for i, template in enumerate(templates, 1):
        click.echo(f"{i}. {template['name']}")
        click.echo(f"   描述: {template['description']}")
        click.echo(f"   Cron: {template['cron_expression']}")
        click.echo(f"   命令: {template['command']}")
        click.echo()


# 调度器管理子命令组
@cli.group()
def scheduler():
    """调度器管理"""
    pass


@scheduler.command('start')
def start_scheduler():
    """启动调度器"""
    cli_manager.start_scheduler()


@scheduler.command('stop')
def stop_scheduler():
    """停止调度器"""
    cli_manager.stop_scheduler()


@scheduler.command('status')
def scheduler_status():
    """查看调度器状态"""
    cli_manager.init_scheduler()
    if not cli_manager.scheduler:
        click.echo("调度器未初始化", err=True)
        return

    status = "运行中" if cli_manager.scheduler.running else "已停止"
    task_count = len(cli_manager.scheduler.tasks)

    click.echo(f"调度器状态: {status}")
    click.echo(f"任务数量: {task_count}")
    click.echo(f"工作线程数: {cli_manager.scheduler.executor._max_workers}")


@scheduler.command('save')
def save_scheduler_state():
    """保存调度器状态"""
    cli_manager.save_state()


@scheduler.command('load')
def load_scheduler_state():
    """加载调度器状态"""
    cli_manager.load_state()


# 配置管理子命令组
@cli.group()
def config():
    """配置管理"""
    pass


@config.command('show')
@click.option('--format', type=click.Choice(['yaml', 'json']), default='yaml', help='输出格式')
def show_config(format):
    """显示当前配置"""
    config_data = cli_manager.config_manager.config
    tasks_data = cli_manager.config_manager.tasks_config

    if format == 'json':
        output = {
            'scheduler_config': config_data,
            'tasks': tasks_data
        }
        click.echo(json.dumps(output, indent=2, ensure_ascii=False))
    else:
        import yaml
        output = {
            'scheduler': config_data.get('scheduler', {}),
            'service': config_data.get('service', {}),
            'tasks': tasks_data
        }
        click.echo(yaml.dump(output, default_flow_style=False, allow_unicode=True, indent=2))


@config.command('export')
@click.argument('file_path')
def export_config(file_path):
    """导出配置"""
    cli_manager.config_manager.export_config(file_path)
    click.echo(f"配置已导出到: {file_path}")


@config.command('import')
@click.argument('file_path')
@click.option('--force', '-f', is_flag=True, help='强制导入，覆盖现有配置')
def import_config(file_path, force):
    """导入配置"""
    if not force and (cli_manager.config_manager.tasks_config or
                      any(cli_manager.config_manager.config.values())):
        if not click.confirm("导入将覆盖现有配置，确定要继续吗？"):
            return

    cli_manager.config_manager.import_config(file_path)
    click.echo(f"配置已从 {file_path} 导入")


# Cron工具子命令组
@cli.group()
def cron():
    """Cron工具"""
    pass


@cron.command('validate')
@click.argument('expression')
def validate_cron(expression):
    """验证Cron表达式"""
    try:
        parser = CronParser(expression)
        click.echo(f"✓ Cron表达式有效")
        click.echo(f"描述: {parser.get_schedule_description()}")

        # 显示下次运行时间
        next_time = parser.get_next_run_time()
        click.echo(f"下次运行: {next_time}")

    except Exception as e:
        click.echo(f"✗ Cron表达式无效: {e}")


@cron.command('examples')
def cron_examples():
    """显示Cron表达式示例"""
    examples = get_cron_examples()

    click.echo("Cron表达式示例:")
    click.echo()

    for expr, desc in examples:
        click.echo(f"表达式: {expr}")
        click.echo(f"说明: {desc}")
        try:
            parser = CronParser(expr)
            click.echo(f"描述: {parser.get_schedule_description()}")
        except:
            pass
        click.echo("-" * 50)


# 服务管理子命令组
@cli.group()
def service():
    """服务管理"""
    pass


@service.command('install')
def install_service():
    """安装Windows服务"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.install_service()
    except ImportError as e:
        click.echo(f"✗ 安装服务失败: 缺少必要的Windows服务库 ({e})", err=True)
        click.echo("请确保已安装 pywin32 库: pip install pywin32", err=True)


@service.command('uninstall')
def uninstall_service():
    """卸载Windows服务"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.uninstall_service()
    except ImportError as e:
        click.echo(f"✗ 卸载服务失败: 缺少必要的Windows服务库 ({e})", err=True)


@service.command('start')
def start_service():
    """启动Windows服务"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.start_service()
    except ImportError as e:
        click.echo(f"✗ 启动服务失败: 缺少必要的Windows服务库 ({e})", err=True)


@service.command('stop')
def stop_service():
    """停止Windows服务"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.stop_service()
    except ImportError as e:
        click.echo(f"✗ 停止服务失败: 缺少必要的Windows服务库 ({e})", err=True)


@service.command('restart')
def restart_service():
    """重启Windows服务"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.restart_service()
    except ImportError as e:
        click.echo(f"✗ 重启服务失败: 缺少必要的Windows服务库 ({e})", err=True)


@service.command('status')
def service_status():
    """查看Windows服务状态"""
    try:
        from windows_service import ServiceManager
        service_manager = ServiceManager()
        service_manager.get_service_status()
    except ImportError as e:
        click.echo(f"✗ 查看服务状态失败: 缺少必要的Windows服务库 ({e})", err=True)


if __name__ == "__main__":
    cli()
